<?php

class Session {

    protected $privateKey = null;  // 验证串加密公钥

    protected $domain = null;

    protected $expire = null;

    protected $path = '/';

    protected $cookieData = array();

    private $_auth = null;

    private $_data = array();

    protected $_datax = array();

    private static $_instance;

    protected $writeCookieData = array();  // 写入到COOKIE中的最终数据

    /**
     * DATA
     */
    protected $dataName = 'DATA_';

    /**
     * AUTH
     */
    protected $authName = 'AUTH_';

    /**
     * SESSIONID
     */
    protected $sessionName = 'SESSIONID_';

    /**
     * SESSIONID
     */
    protected $sessionId = '';

    private function __construct($domain = '', $expire = 3600, $privateKey = '') {
        if (!$domain) {
            $domain = $_SERVER['HTTP_HOST'];
        }

        if (!$expire) {
            $expire = 3600;
        }

        if (!$privateKey) {
            $privateKey = 'CassPas98123dA!@#^$!@!@#311';
        }

        $this->domain = $domain;
        $this->expire = $expire;
        $this->privateKey = md5(date('Y-m-d') . $privateKey);
        $this->_datax = array();

        $id = substr(md5($domain), 5, 5);
        $this->dataName .= $id;
        $this->authName .= $id;
        $this->sessionName .= $id;

        $this->cookieData = array(
            $this->dataName => $_COOKIE[$this->dataName],
            $this->sessionName => $_COOKIE[$this->sessionName]
        );

        $content = $this->cookieData[$this->dataName];
        $sessionId = $this->cookieData[$this->sessionName];

        if ($content) {
            $data = $this->_cipher($content, 'DECODE', $this->privateKey);
            parse_str($data, $this->_data);
        }

        // 新用户
        if (!$this->cookieData || !isset($_COOKIE[$this->sessionName])) {
            $sessionId = md5(uniqid());
            $this->cookieData[$this->sessionName] = $sessionId;
            $this->write();
        }

        $this->sessionId = $sessionId;
    }

    /**
     * 设置 domain
     *
     * @param $domain
     *
     * @returns
     */
    public function setDomain($domain) {

        $this->domain = $domain;
    }

    /**
     * getInstance
     *
     * @return self
     */
    public static function getInstance($domain = '', $expire = 3600, $privateKey = '') {

        if (!(self::$_instance instanceof self)) {
            self::$_instance = new self($domain, $expire, $privateKey);
        }

        return self::$_instance;
    }

    public function get($key) {
        $data = $this->_data;
        if (array_key_exists($key, $data)) {
            return $data[$key];
        }
        return null;
    }

    public function set($key, $value) {
        if (!$key) {
            return false;
        }

        // Logger::debug('set:'. $key . ' value: '. $value);

        $this->_data[$key] = $value;
        return $this->write($this->_data);
    }

    public function getx($key, $default = null) {
        if ($key && array_key_exists($key, $_COOKIE)) {
            return $_COOKIE[$key];
        }
        return $default;
    }

    public function setx($key, $value = null, $expire = 0, $path = '',
                         $domain = '', $isSession = false) {
        if (!$key) {
            return false;
        }
        // Logger::debug('setx:'. $key);

        if (!$domain) {
            $domain = $this->domain;
        }

        if (!$path) {
            $path = $this->path;
        }

        if (!$expire) {
            $expire = $this->expire;
        }

        $this->_datax[$key] = array('value' => $value,
                                    'expire' => $expire,
                                    'path' => $path,
                                    'domain' => $domain,
                                    'isSession' => $isSession
                                   );

        $this->write(null);
        return true;
    }

    public function remove($key) {
        if (!is_string($key)) {
            return false;
        }

        $data = $this->_data;

        if (array_key_exists($key, $this->_data)) {
            unset($this->_data[$key]);
            $this->write();
        } elseif (array_key_exists($key, $_COOKIE))  {
            $this->setx($key, null, -3600, $this->path, $this->domain);
        }
        return true;
    }

    public function destroy() {
        $expired = strtotime('-100 day');
        $writeData[$this->sessionName] = array('value' => '',
                                               'expired' => $expired,
                                               'path' => $this->path,
                                               'domain' => $this->domain);

        $writeData[$this->dataName] = array('value' => '',
                                          'expired' => $expired,
                                          'path' => $this->path,
                                          'domain' => $this->domain);

        $this->cookieData[$this->authName] = '';
        $this->cookieData[$this->sessionName] = '';
        $this->cookieData[$this->dataName] = '';
        $this->_data = array();

        $this->writeCookieData = $writeData;
    }

    protected function write($data = null) {
        $writeData = array();
        $sessionId = $this->cookieData[$this->sessionName];

        if ($data && is_array($data)) {
            $this->_data = array_merge($this->_data, $data);
        }

        $content = http_build_query($this->_data);


        // 加密调整到 send 方法，避免每次调用一次 write 就加密一次
        $this->cookieData[$this->dataName] = $content;
        $cookieExpired = time() + $this->expire;

        // sessionId
        $writeData[$this->sessionName] = array('value' => $sessionId,
                                               'expired' => $cookieExpired,
                                               'path' => $this->path,
                                               'domain' => $this->domain);

        // data
        if ($this->cookieData[$this->dataName]) {
            $writeData[$this->dataName] = array('value' => $this->cookieData[$this->dataName],
                                              'expired' => $cookieExpired,
                                              'path' => $this->path,
                                              // 该cookie是否需要加密
                                              'crypt' => true,
                                              'domain' => $this->domain);
        }

        // 处理原始数据
        if ($this->_datax) {
            foreach ($this->_datax as $key => $value) {
                $writeData[$key] = array('value' => $value['value'],
                                         'expired' => time() + $value['expire'],
                                         'path' => $value['path'],
                                         'domain' => $value['domain']);
                if ($value['isSession']) {
                    unset($writeData[$key]['expired']);
                }
            }
        }

        $this->writeCookieData = $writeData;

        return true;
    }

    /**
     * 启用 session 的时候，普通 cookie 失效，替代方法
     *
     * @param $name
     * @param $value
     * @param $expired
     * @param $path
     * @param $domain
     *
     * @returns
     */
    public function setCookie($name, $value, $expired = 0, $path = '/', $domain = null) {

        $this->writeCookieData[$name] = array(
            'value' => $value,
            'expired' => $expired,
            'path' => $path,
            'domain' => $domain,
        );

        return true;
    }

    public function send($buffer = null) {

        $writeData = $this->writeCookieData;
        if ($writeData && is_array($writeData)) {
            $i = 1;
            foreach ($writeData as $name => $item) {

                if ($item['crypt']) {
                    $item['value'] = $this->_cipher($item['value'], 'ENCODE', $this->privateKey);
                }

                if ($i == 1) {
                    $this->_setCookie($name, $item['value'], $item['expired'],
                                      $item['path'], $item['domain'], true);
                } else {
                    $this->_setCookie($name, $item['value'], $item['expired'],
                                      $item['path'], $item['domain'], false);
                }
                $i ++;
            }
        }

        return $buffer;
    }

    private function _setCookie($name, $value = '', $expired = 0, $path = '/',
                                $domain = '', $replace = false) {
        $cookStr = 'Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
            . '; Path=' . $path
            . '; domain=' . $domain;
        if (isset($expired)) {
            $cookStr .= '; expires=' . gmdate('D, d-M-Y H:i:s \G\M\T', $expired);
        }

        header($cookStr, $replace);

    }

    private function _generateAuthKey($data, $privateKey) {
        return md5($data . $privateKey);
    }

    private function _validateAuthKey($data, $privateKey, $authKey) {
        return $authKey == $this->_generateAuthKey($data, $privateKey);
    }

    private function _cipher($string, $operation, $key = '') {

        $cipher = Crypt::factory('Authcode');
        if ($operation == 'DECODE') {
            return $cipher->decrypt($string, $key);
        } else {
            return $cipher->encrypt($string, $key);
        }
    }

    /**
     * getManyouSessionId
     * 获取唯一标示
     *
     * @returns
     */
    public function getSessionId() {

        return $this->sessionId;
    }

}
